#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
压缩文件处理模块

提供对ZIP、TAR、GZIP、BZIP2等压缩文件格式的处理功能
"""

import os
import zipfile
import tarfile
import gzip
import bz2
import lzma
import shutil
import tempfile
from pathlib import Path
from typing import Union, List, Dict, Any, Optional, Tuple, Iterator, Set

# 导入异常处理装饰器
from ..exception.exception_handler import exception_handler
from ..exception.exceptions import FileException

# 导入日志模块
from ..logger.logger import Logger

# 创建日志记录器
logger = Logger("archive_handler").get_logger()


class ArchiveHandler:
    """
    压缩文件处理类
    
    提供对ZIP、TAR、GZIP、BZIP2等压缩文件格式的处理功能
    """
    
    def __init__(self):
        """
        初始化压缩文件处理器
        """
        pass
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def create_zip(self, source_paths: Union[str, Path, List[Union[str, Path]]], 
                 output_path: Union[str, Path], compression_level: int = 9,
                 include_base_dir: bool = False) -> None:
        """
        创建ZIP压缩文件
        
        Args:
            source_paths: 要压缩的文件或目录路径，可以是单个路径或路径列表
            output_path: 输出的ZIP文件路径
            compression_level: 压缩级别（0-9），0表示不压缩，9表示最大压缩，默认为9
            include_base_dir: 是否包含基目录，默认为False
            
        Raises:
            FileException: 文件不存在或压缩失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 压缩单个文件
            >>> handler.create_zip('example.txt', 'example.zip')
            >>> # 压缩多个文件
            >>> handler.create_zip(['file1.txt', 'file2.txt'], 'files.zip')
            >>> # 压缩目录
            >>> handler.create_zip('my_directory', 'my_directory.zip', include_base_dir=True)
        """
        # 确保source_paths是列表
        if not isinstance(source_paths, list):
            source_paths = [source_paths]
        
        # 转换为Path对象
        source_paths = [Path(p) for p in source_paths]
        output_path = Path(output_path)
        
        # 检查源路径是否存在
        for path in source_paths:
            if not path.exists():
                raise FileException(f"源路径不存在: {path}")
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 创建ZIP文件
            with zipfile.ZipFile(
                output_path, 'w', 
                compression=zipfile.ZIP_DEFLATED, 
                compresslevel=compression_level
            ) as zipf:
                # 处理每个源路径
                for source_path in source_paths:
                    if source_path.is_file():
                        # 添加单个文件
                        zipf.write(source_path, arcname=source_path.name)
                        logger.debug(f"添加文件到ZIP: {source_path}")
                    elif source_path.is_dir():
                        # 添加目录及其内容
                        base_dir = source_path.name if include_base_dir else ''
                        for root, dirs, files in os.walk(source_path):
                            root_path = Path(root)
                            # 计算相对路径
                            if include_base_dir:
                                rel_dir = os.path.relpath(root, source_path.parent)
                            else:
                                rel_dir = os.path.relpath(root, source_path)
                                if rel_dir == '.':
                                    rel_dir = ''
                            
                            # 添加目录（如果不是空字符串）
                            if rel_dir and rel_dir != '.':
                                zipf.write(root_path, arcname=rel_dir)
                            
                            # 添加文件
                            for file in files:
                                file_path = root_path / file
                                if include_base_dir:
                                    archive_path = os.path.join(rel_dir, file)
                                else:
                                    archive_path = os.path.join(rel_dir, file) if rel_dir else file
                                zipf.write(file_path, arcname=archive_path)
                                logger.debug(f"添加文件到ZIP: {file_path} -> {archive_path}")
            
            logger.info(f"ZIP文件创建成功: {output_path}")
        except Exception as e:
            raise FileException(f"创建ZIP文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def extract_zip(self, zip_path: Union[str, Path], output_dir: Union[str, Path], 
                  password: Optional[bytes] = None, specific_files: Optional[List[str]] = None) -> None:
        """
        解压ZIP文件
        
        Args:
            zip_path: ZIP文件路径
            output_dir: 解压输出目录
            password: 解压密码（如果有），默认为None
            specific_files: 要解压的特定文件列表，默认为None（解压所有文件）
            
        Raises:
            FileException: 文件不存在或解压失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 解压所有文件
            >>> handler.extract_zip('example.zip', 'output_dir')
            >>> # 解压带密码的ZIP文件
            >>> handler.extract_zip('protected.zip', 'output_dir', password=b'secret')
            >>> # 只解压特定文件
            >>> handler.extract_zip('example.zip', 'output_dir', specific_files=['file1.txt', 'images/photo.jpg'])
        """
        zip_path = Path(zip_path)
        output_dir = Path(output_dir)
        
        # 检查ZIP文件是否存在
        if not zip_path.exists():
            raise FileException(f"ZIP文件不存在: {zip_path}")
        
        # 确保输出目录存在
        output_dir.mkdir(parents=True, exist_ok=True)
        
        try:
            with zipfile.ZipFile(zip_path, 'r') as zipf:
                # 检查是否需要密码
                if any(info.flag_bits & 0x1 for info in zipf.infolist()) and not password:
                    raise FileException("ZIP文件受密码保护，但未提供密码")
                
                # 获取文件列表
                file_list = zipf.namelist()
                
                # 如果指定了特定文件，检查它们是否存在
                if specific_files:
                    missing_files = [f for f in specific_files if f not in file_list]
                    if missing_files:
                        raise FileException(f"ZIP文件中不存在以下文件: {', '.join(missing_files)}")
                    extract_list = specific_files
                else:
                    extract_list = file_list
                
                # 解压文件
                for file in extract_list:
                    try:
                        zipf.extract(file, path=output_dir, pwd=password)
                        logger.debug(f"解压文件: {file} -> {output_dir}")
                    except RuntimeError as e:
                        if "password required" in str(e).lower() or "bad password" in str(e).lower():
                            raise FileException(f"解压文件 {file} 需要正确的密码")
                        else:
                            raise
            
            logger.info(f"ZIP文件解压成功: {zip_path} -> {output_dir}")
        except zipfile.BadZipFile:
            raise FileException(f"无效的ZIP文件: {zip_path}")
        except Exception as e:
            raise FileException(f"解压ZIP文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def list_zip_contents(self, zip_path: Union[str, Path]) -> List[Dict[str, Any]]:
        """
        列出ZIP文件内容
        
        Args:
            zip_path: ZIP文件路径
            
        Returns:
            包含文件信息的字典列表，每个字典包含文件名、大小、压缩大小、修改时间等信息
            
        Raises:
            FileException: 文件不存在或读取失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> contents = handler.list_zip_contents('example.zip')
            >>> for item in contents:
            ...     print(f"{item['filename']} - {item['size']} bytes")
        """
        zip_path = Path(zip_path)
        
        # 检查ZIP文件是否存在
        if not zip_path.exists():
            raise FileException(f"ZIP文件不存在: {zip_path}")
        
        try:
            result = []
            with zipfile.ZipFile(zip_path, 'r') as zipf:
                for info in zipf.infolist():
                    # 提取文件信息
                    item = {
                        'filename': info.filename,
                        'size': info.file_size,
                        'compressed_size': info.compress_size,
                        'modified': f"{info.date_time[0]}-{info.date_time[1]:02d}-{info.date_time[2]:02d} "
                                   f"{info.date_time[3]:02d}:{info.date_time[4]:02d}:{info.date_time[5]:02d}",
                        'is_dir': info.filename.endswith('/'),
                        'compression_type': info.compress_type,
                        'is_encrypted': bool(info.flag_bits & 0x1),
                    }
                    result.append(item)
            
            logger.info(f"列出ZIP文件内容成功: {zip_path} (共 {len(result)} 个项目)")
            return result
        except zipfile.BadZipFile:
            raise FileException(f"无效的ZIP文件: {zip_path}")
        except Exception as e:
            raise FileException(f"列出ZIP文件内容失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def create_tar(self, source_paths: Union[str, Path, List[Union[str, Path]]], 
                 output_path: Union[str, Path], compression: str = 'gz',
                 include_base_dir: bool = False) -> None:
        """
        创建TAR压缩文件
        
        Args:
            source_paths: 要压缩的文件或目录路径，可以是单个路径或路径列表
            output_path: 输出的TAR文件路径
            compression: 压缩方式，可选 'gz'、'bz2'、'xz' 或 None（不压缩），默认为'gz'
            include_base_dir: 是否包含基目录，默认为False
            
        Raises:
            FileException: 文件不存在或压缩失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 创建tar.gz文件
            >>> handler.create_tar('my_directory', 'archive.tar.gz')
            >>> # 创建tar.bz2文件
            >>> handler.create_tar(['file1.txt', 'file2.txt'], 'archive.tar.bz2', compression='bz2')
            >>> # 创建不压缩的tar文件
            >>> handler.create_tar('my_directory', 'archive.tar', compression=None)
        """
        # 确保source_paths是列表
        if not isinstance(source_paths, list):
            source_paths = [source_paths]
        
        # 转换为Path对象
        source_paths = [Path(p) for p in source_paths]
        output_path = Path(output_path)
        
        # 检查源路径是否存在
        for path in source_paths:
            if not path.exists():
                raise FileException(f"源路径不存在: {path}")
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        # 设置压缩模式
        mode = 'w:'
        if compression == 'gz':
            mode = 'w:gz'
        elif compression == 'bz2':
            mode = 'w:bz2'
        elif compression == 'xz':
            mode = 'w:xz'
        
        try:
            # 创建TAR文件
            with tarfile.open(output_path, mode) as tar:
                # 处理每个源路径
                for source_path in source_paths:
                    if source_path.is_file():
                        # 添加单个文件
                        tar.add(source_path, arcname=source_path.name)
                        logger.debug(f"添加文件到TAR: {source_path}")
                    elif source_path.is_dir():
                        # 添加目录及其内容
                        if include_base_dir:
                            # 包含基目录
                            tar.add(source_path, arcname=source_path.name)
                            logger.debug(f"添加目录到TAR: {source_path} -> {source_path.name}")
                        else:
                            # 不包含基目录，只添加内容
                            for item in source_path.iterdir():
                                tar.add(item, arcname=item.relative_to(source_path.parent))
                                logger.debug(f"添加项目到TAR: {item} -> {item.relative_to(source_path.parent)}")
            
            logger.info(f"TAR文件创建成功: {output_path}")
        except Exception as e:
            raise FileException(f"创建TAR文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def extract_tar(self, tar_path: Union[str, Path], output_dir: Union[str, Path],
                  specific_files: Optional[List[str]] = None) -> None:
        """
        解压TAR文件
        
        Args:
            tar_path: TAR文件路径
            output_dir: 解压输出目录
            specific_files: 要解压的特定文件列表，默认为None（解压所有文件）
            
        Raises:
            FileException: 文件不存在或解压失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 解压所有文件
            >>> handler.extract_tar('archive.tar.gz', 'output_dir')
            >>> # 只解压特定文件
            >>> handler.extract_tar('archive.tar.bz2', 'output_dir', specific_files=['file1.txt', 'images/photo.jpg'])
        """
        tar_path = Path(tar_path)
        output_dir = Path(output_dir)
        
        # 检查TAR文件是否存在
        if not tar_path.exists():
            raise FileException(f"TAR文件不存在: {tar_path}")
        
        # 确保输出目录存在
        output_dir.mkdir(parents=True, exist_ok=True)
        
        try:
            # 自动检测压缩格式
            with tarfile.open(tar_path, 'r:*') as tar:
                # 获取文件列表
                file_list = tar.getnames()
                
                # 如果指定了特定文件，检查它们是否存在
                if specific_files:
                    missing_files = [f for f in specific_files if f not in file_list]
                    if missing_files:
                        raise FileException(f"TAR文件中不存在以下文件: {', '.join(missing_files)}")
                    extract_list = [tar.getmember(f) for f in specific_files]
                else:
                    extract_list = None  # 解压所有文件
                
                # 解压文件
                tar.extractall(path=output_dir, members=extract_list)
            
            logger.info(f"TAR文件解压成功: {tar_path} -> {output_dir}")
        except tarfile.ReadError:
            raise FileException(f"无效的TAR文件: {tar_path}")
        except Exception as e:
            raise FileException(f"解压TAR文件失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def list_tar_contents(self, tar_path: Union[str, Path]) -> List[Dict[str, Any]]:
        """
        列出TAR文件内容
        
        Args:
            tar_path: TAR文件路径
            
        Returns:
            包含文件信息的字典列表，每个字典包含文件名、大小、类型、修改时间等信息
            
        Raises:
            FileException: 文件不存在或读取失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> contents = handler.list_tar_contents('archive.tar.gz')
            >>> for item in contents:
            ...     print(f"{item['name']} - {item['size']} bytes")
        """
        tar_path = Path(tar_path)
        
        # 检查TAR文件是否存在
        if not tar_path.exists():
            raise FileException(f"TAR文件不存在: {tar_path}")
        
        try:
            result = []
            with tarfile.open(tar_path, 'r:*') as tar:
                for member in tar.getmembers():
                    # 提取文件信息
                    item = {
                        'name': member.name,
                        'size': member.size,
                        'modified': member.mtime,
                        'is_dir': member.isdir(),
                        'is_file': member.isfile(),
                        'is_symlink': member.issym() or member.islnk(),
                        'mode': member.mode,
                        'uid': member.uid,
                        'gid': member.gid,
                        'uname': member.uname,
                        'gname': member.gname,
                    }
                    result.append(item)
            
            logger.info(f"列出TAR文件内容成功: {tar_path} (共 {len(result)} 个项目)")
            return result
        except tarfile.ReadError:
            raise FileException(f"无效的TAR文件: {tar_path}")
        except Exception as e:
            raise FileException(f"列出TAR文件内容失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def compress_gzip(self, source_path: Union[str, Path], output_path: Union[str, Path] = None,
                    compression_level: int = 9) -> None:
        """
        使用GZIP压缩单个文件
        
        Args:
            source_path: 要压缩的文件路径
            output_path: 输出的GZIP文件路径，默认为None（添加.gz后缀）
            compression_level: 压缩级别（0-9），0表示不压缩，9表示最大压缩，默认为9
            
        Raises:
            FileException: 文件不存在或压缩失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 使用默认输出路径
            >>> handler.compress_gzip('example.txt')  # 创建 example.txt.gz
            >>> # 指定输出路径
            >>> handler.compress_gzip('example.txt', 'compressed.gz')
            >>> # 指定压缩级别
            >>> handler.compress_gzip('example.txt', compression_level=5)
        """
        source_path = Path(source_path)
        
        # 检查源文件是否存在
        if not source_path.exists():
            raise FileException(f"源文件不存在: {source_path}")
        
        # 检查源路径是否为文件
        if not source_path.is_file():
            raise FileException(f"GZIP只能压缩单个文件，不能压缩目录: {source_path}")
        
        # 如果未指定输出路径，添加.gz后缀
        if output_path is None:
            output_path = source_path.with_suffix(source_path.suffix + '.gz')
        else:
            output_path = Path(output_path)
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 创建GZIP文件
            with open(source_path, 'rb') as f_in:
                with gzip.open(output_path, 'wb', compresslevel=compression_level) as f_out:
                    shutil.copyfileobj(f_in, f_out)
            
            logger.info(f"GZIP压缩成功: {source_path} -> {output_path}")
        except Exception as e:
            raise FileException(f"GZIP压缩失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def decompress_gzip(self, gzip_path: Union[str, Path], output_path: Union[str, Path] = None) -> None:
        """
        解压GZIP文件
        
        Args:
            gzip_path: GZIP文件路径
            output_path: 解压输出文件路径，默认为None（移除.gz后缀）
            
        Raises:
            FileException: 文件不存在或解压失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 使用默认输出路径
            >>> handler.decompress_gzip('example.txt.gz')  # 创建 example.txt
            >>> # 指定输出路径
            >>> handler.decompress_gzip('compressed.gz', 'decompressed.txt')
        """
        gzip_path = Path(gzip_path)
        
        # 检查GZIP文件是否存在
        if not gzip_path.exists():
            raise FileException(f"GZIP文件不存在: {gzip_path}")
        
        # 如果未指定输出路径，移除.gz后缀
        if output_path is None:
            # 如果文件名以.gz结尾
            if gzip_path.name.endswith('.gz'):
                output_path = gzip_path.with_suffix('')
            else:
                # 否则添加.ungz后缀
                output_path = gzip_path.with_suffix(gzip_path.suffix + '.ungz')
        else:
            output_path = Path(output_path)
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 解压GZIP文件
            with gzip.open(gzip_path, 'rb') as f_in:
                with open(output_path, 'wb') as f_out:
                    shutil.copyfileobj(f_in, f_out)
            
            logger.info(f"GZIP解压成功: {gzip_path} -> {output_path}")
        except gzip.BadGzipFile:
            raise FileException(f"无效的GZIP文件: {gzip_path}")
        except Exception as e:
            raise FileException(f"GZIP解压失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def compress_bzip2(self, source_path: Union[str, Path], output_path: Union[str, Path] = None,
                     compression_level: int = 9) -> None:
        """
        使用BZIP2压缩单个文件
        
        Args:
            source_path: 要压缩的文件路径
            output_path: 输出的BZIP2文件路径，默认为None（添加.bz2后缀）
            compression_level: 压缩级别（1-9），1表示最快压缩，9表示最大压缩，默认为9
            
        Raises:
            FileException: 文件不存在或压缩失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 使用默认输出路径
            >>> handler.compress_bzip2('example.txt')  # 创建 example.txt.bz2
            >>> # 指定输出路径
            >>> handler.compress_bzip2('example.txt', 'compressed.bz2')
            >>> # 指定压缩级别
            >>> handler.compress_bzip2('example.txt', compression_level=5)
        """
        source_path = Path(source_path)
        
        # 检查源文件是否存在
        if not source_path.exists():
            raise FileException(f"源文件不存在: {source_path}")
        
        # 检查源路径是否为文件
        if not source_path.is_file():
            raise FileException(f"BZIP2只能压缩单个文件，不能压缩目录: {source_path}")
        
        # 如果未指定输出路径，添加.bz2后缀
        if output_path is None:
            output_path = source_path.with_suffix(source_path.suffix + '.bz2')
        else:
            output_path = Path(output_path)
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 创建BZIP2文件
            with open(source_path, 'rb') as f_in:
                with bz2.open(output_path, 'wb', compresslevel=compression_level) as f_out:
                    shutil.copyfileobj(f_in, f_out)
            
            logger.info(f"BZIP2压缩成功: {source_path} -> {output_path}")
        except Exception as e:
            raise FileException(f"BZIP2压缩失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def decompress_bzip2(self, bzip2_path: Union[str, Path], output_path: Union[str, Path] = None) -> None:
        """
        解压BZIP2文件
        
        Args:
            bzip2_path: BZIP2文件路径
            output_path: 解压输出文件路径，默认为None（移除.bz2后缀）
            
        Raises:
            FileException: 文件不存在或解压失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 使用默认输出路径
            >>> handler.decompress_bzip2('example.txt.bz2')  # 创建 example.txt
            >>> # 指定输出路径
            >>> handler.decompress_bzip2('compressed.bz2', 'decompressed.txt')
        """
        bzip2_path = Path(bzip2_path)
        
        # 检查BZIP2文件是否存在
        if not bzip2_path.exists():
            raise FileException(f"BZIP2文件不存在: {bzip2_path}")
        
        # 如果未指定输出路径，移除.bz2后缀
        if output_path is None:
            # 如果文件名以.bz2结尾
            if bzip2_path.name.endswith('.bz2'):
                output_path = bzip2_path.with_suffix('')
            else:
                # 否则添加.unbz2后缀
                output_path = bzip2_path.with_suffix(bzip2_path.suffix + '.unbz2')
        else:
            output_path = Path(output_path)
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 解压BZIP2文件
            with bz2.open(bzip2_path, 'rb') as f_in:
                with open(output_path, 'wb') as f_out:
                    shutil.copyfileobj(f_in, f_out)
            
            logger.info(f"BZIP2解压成功: {bzip2_path} -> {output_path}")
        except Exception as e:
            raise FileException(f"BZIP2解压失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def compress_lzma(self, source_path: Union[str, Path], output_path: Union[str, Path] = None,
                    preset: int = 9) -> None:
        """
        使用LZMA（XZ）压缩单个文件
        
        Args:
            source_path: 要压缩的文件路径
            output_path: 输出的LZMA文件路径，默认为None（添加.xz后缀）
            preset: 压缩预设级别（0-9），0表示最快压缩，9表示最大压缩，默认为9
            
        Raises:
            FileException: 文件不存在或压缩失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 使用默认输出路径
            >>> handler.compress_lzma('example.txt')  # 创建 example.txt.xz
            >>> # 指定输出路径
            >>> handler.compress_lzma('example.txt', 'compressed.xz')
            >>> # 指定压缩级别
            >>> handler.compress_lzma('example.txt', preset=5)
        """
        source_path = Path(source_path)
        
        # 检查源文件是否存在
        if not source_path.exists():
            raise FileException(f"源文件不存在: {source_path}")
        
        # 检查源路径是否为文件
        if not source_path.is_file():
            raise FileException(f"LZMA只能压缩单个文件，不能压缩目录: {source_path}")
        
        # 如果未指定输出路径，添加.xz后缀
        if output_path is None:
            output_path = source_path.with_suffix(source_path.suffix + '.xz')
        else:
            output_path = Path(output_path)
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 创建LZMA文件
            with open(source_path, 'rb') as f_in:
                with lzma.open(output_path, 'wb', preset=preset) as f_out:
                    shutil.copyfileobj(f_in, f_out)
            
            logger.info(f"LZMA压缩成功: {source_path} -> {output_path}")
        except Exception as e:
            raise FileException(f"LZMA压缩失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def decompress_lzma(self, lzma_path: Union[str, Path], output_path: Union[str, Path] = None) -> None:
        """
        解压LZMA（XZ）文件
        
        Args:
            lzma_path: LZMA文件路径
            output_path: 解压输出文件路径，默认为None（移除.xz后缀）
            
        Raises:
            FileException: 文件不存在或解压失败时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> # 使用默认输出路径
            >>> handler.decompress_lzma('example.txt.xz')  # 创建 example.txt
            >>> # 指定输出路径
            >>> handler.decompress_lzma('compressed.xz', 'decompressed.txt')
        """
        lzma_path = Path(lzma_path)
        
        # 检查LZMA文件是否存在
        if not lzma_path.exists():
            raise FileException(f"LZMA文件不存在: {lzma_path}")
        
        # 如果未指定输出路径，移除.xz后缀
        if output_path is None:
            # 如果文件名以.xz结尾
            if lzma_path.name.endswith('.xz'):
                output_path = lzma_path.with_suffix('')
            else:
                # 否则添加.unxz后缀
                output_path = lzma_path.with_suffix(lzma_path.suffix + '.unxz')
        else:
            output_path = Path(output_path)
        
        # 确保输出目录存在
        output_path.parent.mkdir(parents=True, exist_ok=True)
        
        try:
            # 解压LZMA文件
            with lzma.open(lzma_path, 'rb') as f_in:
                with open(output_path, 'wb') as f_out:
                    shutil.copyfileobj(f_in, f_out)
            
            logger.info(f"LZMA解压成功: {lzma_path} -> {output_path}")
        except lzma.LZMAError:
            raise FileException(f"无效的LZMA文件: {lzma_path}")
        except Exception as e:
            raise FileException(f"LZMA解压失败: {e}")
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def is_archive_file(self, file_path: Union[str, Path]) -> bool:
        """
        检查文件是否为压缩文件
        
        Args:
            file_path: 文件路径
            
        Returns:
            如果文件是压缩文件，返回True；否则返回False
            
        Raises:
            FileException: 文件不存在时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> handler.is_archive_file('example.zip')
            True
            >>> handler.is_archive_file('example.txt')
            False
        """
        file_path = Path(file_path)
        
        # 检查文件是否存在
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        # 检查文件是否为文件
        if not file_path.is_file():
            return False
        
        # 检查文件扩展名
        extension = file_path.suffix.lower()
        archive_extensions = {
            '.zip', '.tar', '.gz', '.bz2', '.xz', '.tgz', '.tbz2', '.txz',
            '.7z', '.rar', '.lzma', '.Z', '.lz', '.lzo', '.lz4', '.zst'
        }
        
        # 检查复合扩展名（如.tar.gz）
        if extension in {'.gz', '.bz2', '.xz'} and file_path.stem.endswith('.tar'):
            return True
        
        return extension in archive_extensions
    
    @exception_handler(exception_type=Exception, reraise_as=FileException)
    def get_archive_type(self, file_path: Union[str, Path]) -> str:
        """
        获取压缩文件类型
        
        Args:
            file_path: 文件路径
            
        Returns:
            压缩文件类型，如'zip'、'tar'、'gzip'等；如果不是压缩文件，返回'unknown'
            
        Raises:
            FileException: 文件不存在时抛出
            
        Examples:
            >>> handler = ArchiveHandler()
            >>> handler.get_archive_type('example.zip')
            'zip'
            >>> handler.get_archive_type('archive.tar.gz')
            'tar.gz'
            >>> handler.get_archive_type('example.txt')
            'unknown'
        """
        file_path = Path(file_path)
        
        # 检查文件是否存在
        if not file_path.exists():
            raise FileException(f"文件不存在: {file_path}")
        
        # 检查文件是否为文件
        if not file_path.is_file():
            return 'unknown'
        
        # 获取文件名和扩展名
        name = file_path.name.lower()
        
        # 检查复合扩展名
        if name.endswith('.tar.gz') or name.endswith('.tgz'):
            return 'tar.gz'
        elif name.endswith('.tar.bz2') or name.endswith('.tbz2'):
            return 'tar.bz2'
        elif name.endswith('.tar.xz') or name.endswith('.txz'):
            return 'tar.xz'
        
        # 检查单一扩展名
        extension = file_path.suffix.lower()
        archive_types = {
            '.zip': 'zip',
            '.tar': 'tar',
            '.gz': 'gzip',
            '.bz2': 'bzip2',
            '.xz': 'lzma',
            '.7z': '7zip',
            '.rar': 'rar',
            '.lzma': 'lzma',
            '.Z': 'compress',
            '.lz': 'lzip',
            '.lzo': 'lzop',
            '.lz4': 'lz4',
            '.zst': 'zstd'
        }
        
        return archive_types.get(extension, 'unknown')